package algo_annotations.data.sites;

import algo_annotations.data.Data_a;
import algo_annotations.data.annotations.Anno;
import algo_annotations.data.contrainte_superposition.GroupeSegmentFixe;
import algo_annotations.data.contrainte_superposition.SegmentFixe;
import algo_annotations.params.Params_a;
import algo_annotations.params.objects.ContrainteGeomAnno;
import commons.data.EchelleGeneralization;
import commons.data.Attributes;
import commons.data.I_Searchable;
import commons.data.Model;
import commons.drawing.I_Drawable;
import commons.ihm.ParametresAffichageCarto;
import commons.ihm.ParametresAffichageCarto.ModeAffichage;
import commons.io.in.Point_in;
import commons.logs.Logger;
import commons.data.Zones;
import commons.utils.UtilsCalcul;
import commons.utils.UtilsGeom;
import commons.utils.primitive_geom.Point_d;
import commons.utils.primitive_geom.Rect_d;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.Shape;
import java.awt.geom.Ellipse2D;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.regex.Pattern;


public class Site extends Point_d implements I_Drawable, I_Searchable {
	
	public Data_a data;

	// L'ensemble des paramètres du traitement
	public Params_a params;

	// ZONE
	protected Zones zones;
	public Zones getZones() {return zones;}

	// PARAMETRE GEOMETRIQUE (=hauteur de l'annotation, etc.)
	protected ContrainteGeomAnno contrainteGeom;
	private void setContrainteGeom(ContrainteGeomAnno cga) {
		this.contrainteGeom = cga;
		if (cga.hauteurAnno_papier_mm<=0) // = l'utilisateur ne veut pas voir d'anno pour ce site
			libelle = "";
	}
	public ContrainteGeomAnno getContrainteGeom() {return contrainteGeom;}

	// ECHELLE DE GENERALISATION
	protected EchelleGeneralization eg;
	public EchelleGeneralization getEchelleGeneralization() {return eg;}
	private void setEchelleGeneralization(EchelleGeneralization eg) {this.eg = eg; updateEchelle();}


	//index du site dans le vecteur global des sites
	public int index;

	public double rayonSite_reel_m;
	public double hauteurAnno_1L_reel_m; //si sur une seule ligne
	public double largeurAnno_1L_reel_m; //si sur une seule ligne
	
	public double espaceProtectionAnno_Max_reel_m;
	public double espaceProtectionAnno_superpositionLigneSite_xL_reel_m;
	public double espaceProtectionAnno_superpositionAnno_1L_reel_m;
	public double espaceProtectionAnno_superpositionAnno_2L_reel_m;

	public double distBordSiteIdeale_reel_m;
	public double distBordSiteMax_reel_m;

	public String libelle;
	public boolean bCanBeMultiline;

	private Color color;
	public Color getColor() {return color;}
	public void setColor (Color color) {this.color = color;}

	private String log; // diverses info sur le Site qui s'afficheront dans la fenetre d'info du Site

	//ATTRIBUTS
	protected Attributes attributes;
	public Attributes getAttributes() {return attributes;}
	public Model getModel() {return attributes.getModel();}

	// rect de l'ensemble des positions de l'anno autour du site
	public Rect_d rectPositionsAnno_reel_m;
	// idem mais grossi de l'espace de protection autour des anno (utile pour la phase d'optimisation)
	public Rect_d rectMax_reel_m;



	// PRE-PLACEMENT ( pour la passe 1)

	// Meilleure position de l'anno, independamment des autres annos
	// -> cette position peut entrainer une intersection ou des ambiguite avec d'autres annos !
	public float geneX_bestPlacement;
	public float geneY_bestPlacement;
	public float geneML_bestPlacement;
	public double note_bestPlacement;
	public Anno anno_bestPlacement;
	public boolean bBestPlacementHasPossibleInteraction;

	// Position presque optimale et qui assure l'independance totale avec les autres annos
	public boolean bHasFoundPlacement_withnoInteract; // il est possible de ne pas trouver
	public float geneX_almostbestPlacement_noIntereact;
	public float geneY_almostbestPlacement_noIntereact;
	public float geneML_almostbestPlacement_noIntereact;
	public double note_almostbestPlacement_noIntereact;
	public Anno anno_almostbestPlacement_noIntereact;


	// indique si on trouve un pre-placement pour le site, lequel est-ce et sa note
	public enum TypePreplacement {NONE, BEST, ALMOSTBEST};
	public TypePreplacement typePreplacement = TypePreplacement.NONE;
	public boolean bIsPreplace() {return typePreplacement!=TypePreplacement.NONE;}
	public float geneX_prePlacementIndependant;
	public float geneY_prePlacementIndependant;
	public float geneML_prePlacementIndependant;
	public double notePrePlacement;
	public Anno anno_prePlacementIndependant; // pas forcement le meilleur si le meilleur avait des interaction avec les autres sites

	public void setPrePlaceCarIndependant (TypePreplacement typePreplacement, Anno anno_prePlacement, float geneX, float geneY, float geneML, double note) {
		this.typePreplacement = typePreplacement;
		//bPrePlacementIndependant = true;
		geneX_prePlacementIndependant = geneX;
		geneY_prePlacementIndependant = geneY;
		geneML_prePlacementIndependant = geneML;
		anno_prePlacementIndependant = anno_prePlacement;
		notePrePlacement = note;

		// on reduit le rect d'emprise du site, ce qui va liberer de la place pour les autres
		//  dans la suite du traitement de decoupage du probleme
		rectPositionsAnno_reel_m = anno_prePlacement.getCopy();
		rectMax_reel_m = anno_prePlacement.rect_protection_superpositionAnno;
	}


	// -------------- Donnee d'optimisation ------------------------------------
	public static final int SSRECT_FULL = 0;
	public static final int SSRECT_RIGHT = 1;
	public static final int SSRECT_LEFT = 2;
	public static final int SSRECT_UP = 3;
	public static final int SSRECT_DOWN = 4;
	public static final int SSRECT_UPRIGHT = 5;
	public static final int SSRECT_UPLEFT = 6;
	public static final int SSRECT_DOWNRIGHT = 7;
	public static final int SSRECT_DOWNLEFT = 8;
	public static final int NB_SSRECT = 9;

	// tableau des segments qui peuvent potentiellement intersecter une anno de ce site
	public SegmentFixe[][] tab_tabSegmentIntersectable;
	// tableau des sites dont l'anno peut intersecter l'anno ou croiser le lien_anno_site de l'anno de ce site
	public Site[][] tab_tabSiteIntersectAnno;
	
	// tableau des sites qui peuvent se superposer a l'anno de ce site
	public Site[] tabSiteSuperposables;

	// tableau des sites qui peuvent etre ambigus
	public Site[] tabSiteAmbiguiteMax_Anno_Site_memeCouleur;
	public Site[] tabSiteAmbiguiteMax_Anno_Site_autreCouleur;
	public Site[] tabSiteAmbiguiteMedium_Anno_Site_memeCouleur;
	public Site[] tabSiteAmbiguiteMedium_Anno_Site_autreCouleur;
	public Site[] tabSiteAmbiguiteSmall_Anno_Site_memeCouleur;
	public Site[] tabSiteAmbiguiteSmall_Anno_Site_autreCouleur;

	public Site[] tabSiteAmbiguiteMax_Site_Anno_memeCouleur;
	public Site[] tabSiteAmbiguiteMax_Site_Anno_autreCouleur;
	public Site[] tabSiteAmbiguiteMedium_Site_Anno_memeCouleur;
	public Site[] tabSiteAmbiguiteMedium_Site_Anno_autreCouleur;
	public Site[] tabSiteAmbiguiteSmall_Site_Anno_memeCouleur;
	public Site[] tabSiteAmbiguiteSmall_Site_Anno_autreCouleur;

	// Liste des sites uniques en interaction
	public HashSet<Site> setSiteInteraction;
	// idem mais sans les ambiguite autre couleur (peu impactantes, et donc ne pas les prendre en compte
	//  dans certains pre-traitements peut faire gagner en efficacité)
	public HashSet<Site> setSiteInteraction_woAutreCouleur;

	public double factorMedium = 1.0/3;
	public double factorSmall  = 1.0/5;


	public Site (Data_a data, Point_in pt_in, Params_a params) {
		super(pt_in);
		this.params = params;
		this.data = data;
		this.attributes = pt_in.getAttributes_in();
		this.libelle = attributes.getValue(params.nomChampLibelle).trim();
		this.zones = pt_in.zones;
		this.setContrainteGeom(pt_in.contrainteGeomAnno);
		this.setEchelleGeneralization(pt_in.echelleGeneralization);
		this.color = pt_in.color;
		
		if (params.bUseMultiligne)
			precalculCoupureLibelle();
		else
			bCanBeMultiline = false;
		
		log = "";
	}

	// 2 possibilités pour qu'une site soit considéré comme sans anno : pas de libelle, ou bien facteur correctif d'anno = 0
	public boolean hasAnno() {
		return libelle.length()>0 && eg.facteurTaille>0;
	}

	// Appelé dès qu'on définit une échelle de généralisation
	// Tous les éléments de dimension paramétrés par l'utilisateur, exprimé en unité papier (mm) ou en facteur de taille
	//  sont convertis en mètre réels, grace à l'échelle, pour les calculs ultérieurs
	// On calule notamment les rectangle max de déplacement de l'anno
	private void updateEchelle() {
		// c'est une erreur bloquante, mais elle est traitée par la suite par PreTraitementInputData
		if (contrainteGeom==null) return;

		// on ne multiplie pas par eg.facteurTaille, qui est réservé à l'anno
		rayonSite_reel_m = (float) (contrainteGeom.diametreSite_papier_mm/2 * eg.echelle_inv / 1000.0);

		if (hasAnno()) {
			hauteurAnno_1L_reel_m = (float) (contrainteGeom.hauteurAnno_papier_mm * eg.echelle_inv / 1000.0 * eg.facteurTaille);
			largeurAnno_1L_reel_m = (float) params.myFont.getTextWidth(libelle, hauteurAnno_1L_reel_m);

			// Distance ideale entre anno et le bord de son site
			distBordSiteIdeale_reel_m = contrainteGeom.fact_distIdealeAnnoSite * hauteurAnno_1L_reel_m;
			distBordSiteMax_reel_m = contrainteGeom.fact_distMaxAnnoSite * hauteurAnno_1L_reel_m;

			// Pour l'espace de protection LigneSite, on se replace à l'échelle sans le facteur correctif de l'anno
			//  en effet, il s'agit d'une protection visant entre autres a palier l'imprecision du tracé des lignes, qui elles sont à l'échelle
			espaceProtectionAnno_superpositionLigneSite_xL_reel_m = hauteurAnno_1L_reel_m/eg.facteurTaille * params.facteurProtection_superpositionLigneSite;
			espaceProtectionAnno_superpositionAnno_1L_reel_m = hauteurAnno_1L_reel_m * params.facteurProtection_superpositionAnno_1L;
			espaceProtectionAnno_superpositionAnno_2L_reel_m = hauteurAnno_1L_reel_m * params.facteurProtection_superpositionAnno_2L;

			// Rectangle max autour du site pour le calcul d'optimisation
			double facteurProtectionMax = params.facteurProtection_superpositionLigneSite;
			facteurProtectionMax = Math.max(facteurProtectionMax, params.facteurProtection_superpositionAnno_1L);
			facteurProtectionMax = Math.max(facteurProtectionMax, params.facteurProtection_superpositionAnno_2L);
			espaceProtectionAnno_Max_reel_m = hauteurAnno_1L_reel_m * facteurProtectionMax;

			// largeur max (ie. si anno sur 1 ligne)
			double largeur = 2 * (rayonSite_reel_m + distBordSiteMax_reel_m + largeurAnno_1L_reel_m);
			//hauteur max (ie. si anno sur 2 lignes si option choisie)
			double hauteur1Anno;
			if (params.bUseMultiligne)
				hauteur1Anno = (2+params.facteurEspaceInterligne) * hauteurAnno_1L_reel_m;
			else
				hauteur1Anno = hauteurAnno_1L_reel_m;
			double hauteur = 2 * (rayonSite_reel_m + distBordSiteMax_reel_m + hauteur1Anno);

			rectPositionsAnno_reel_m = new Rect_d(this, largeur, hauteur);
			rectMax_reel_m = rectPositionsAnno_reel_m.getCopy().expand(espaceProtectionAnno_Max_reel_m, espaceProtectionAnno_Max_reel_m);

		} else {
			// pas d'annotation pour ce site
			hauteurAnno_1L_reel_m = 0;
			largeurAnno_1L_reel_m = 0;
			distBordSiteIdeale_reel_m = 0;
			distBordSiteMax_reel_m = 0;
			rectPositionsAnno_reel_m = new Rect_d(x, y, 0);
			rectMax_reel_m = rectPositionsAnno_reel_m.getCopy();
		}
	}

	

	//**************************************************************************
	//  COUPURE DU LIBELLE EN PLUSIEURS MOTS POUR LE MULTILIGNE
	//**************************************************************************

	private ArrayList<ConfigCoupureLibelle> vConfigCoupureLibelle;
	private void precalculCoupureLibelle() {
		vConfigCoupureLibelle = new ArrayList<ConfigCoupureLibelle>();
		int current_index=0;

		while (true) {

			//recherche prochaine coupure
			int index1 = libelle.indexOf(" ", current_index);
			int index2 = libelle.indexOf("-", current_index);
			int indexMin;
			if (index1<0 && index2<0) break;
			if (index1<0) indexMin = index2;
			else if(index2<0) indexMin=index1;
			else indexMin = Math.min(index1, index2);

			String typeCoupure = libelle.substring(indexMin, indexMin+1);

			if (typeCoupure.equals(" ")) {
				String mot1 = libelle.substring(0,indexMin).trim(); // on ne prend pas l'espace ...
				String mot2 = libelle.substring(indexMin+1).trim();
				if (mot1.length()>0 && mot2.length()>0 && !containsAlreadyThisConfigCoupure(mot1, mot2))
					vConfigCoupureLibelle.add(new ConfigCoupureLibelle(mot1,mot2));
			} else if (typeCoupure.equals("-")) {
				String mot1 = libelle.substring(0,indexMin+1).trim(); // on prend le "-" au niveau du 1er mot
				String mot2 = libelle.substring(indexMin+1).trim();
				if (mot1.length()>0 && mot2.length()>0 && !containsAlreadyThisConfigCoupure(mot1, mot2))
					vConfigCoupureLibelle.add(new ConfigCoupureLibelle(mot1,mot2));
			}

			current_index = indexMin+1;
		}
		
		bCanBeMultiline = !vConfigCoupureLibelle.isEmpty();
	}

	// si un mot contient un double espace, par ex., il est possible que l'on tente de créer 2 config identiques !
	private boolean containsAlreadyThisConfigCoupure(String ligne1, String ligne2) {
		for (ConfigCoupureLibelle c : vConfigCoupureLibelle)
			if (c.ligne1.equals(ligne1) && c.ligne2.equals(ligne2))
				return true;
		return false;
	}

	public ConfigCoupureLibelle getConfigCoupureFromGene(double ml_n) {
		if (!params.bUseMultiligne || !bCanBeMultiline || ml_n==-1)
			return null;

		// la suite ne fonctionne que si ml_n est dans [0;1[
		if (ml_n==1) ml_n=0.999;

		int nbConfig = vConfigCoupureLibelle.size();
		int whichConfig = (int) (ml_n * (nbConfig+1));

		if (whichConfig==0)
			return null; // codage du souhait 1 ligne
		else
			return vConfigCoupureLibelle.get(whichConfig-1);
	}

	public class ConfigCoupureLibelle {
		public String ligne1;
		public String ligne2;
		public double largeurLigne1_reel_m;
		public double largeurLigne2_reel_m;

		public ConfigCoupureLibelle(String ligne1, String ligne2) {
			this.ligne1 = ligne1;
			this.ligne2 = ligne2;
			largeurLigne1_reel_m = params.myFont.getTextWidth(ligne1, hauteurAnno_1L_reel_m);
			largeurLigne2_reel_m = params.myFont.getTextWidth(ligne2, hauteurAnno_1L_reel_m);
		}
		@Override
		public String toString() {return "[" + ligne1 + "] [" + ligne2 + "]";}
	}

	private String printAllConfigCoupureLibelle() {
		if (!bCanBeMultiline) return " libelle non multiline";

		String res = "";
		int i=1;
		for (ConfigCoupureLibelle c : vConfigCoupureLibelle)
			res += "" + (i++) + " : " + c + "\n";
		return res;
	}


	//**************************************************************************
	// DECOUPAGE DU PROBLEME ET PRE-PLACEMENT DES ANNOS
	//**************************************************************************

	// calcul l'emplacement optimal, independament des autres annos
	public void computeBestPlacement_indepAutreAnno(int nbTestPlacemet_X, int nbTestPlacemet_Y) {
		if (!hasAnno()) return;

		// On teste un grand nombre de position de l'anno pour trouver la meilleure
		// position (hors interaction avec autres anno (intersection/ambiguite)
		double[] tempData = new double[4];

		geneX_bestPlacement=0;
		geneY_bestPlacement=0;
		geneML_bestPlacement=0;
		note_bestPlacement = 1;
		anno_bestPlacement = new Anno(this);

		// nb de config multiligne
		int nbTotal_ConfigML;
		if (bCanBeMultiline)
			nbTotal_ConfigML = vConfigCoupureLibelle.size()+1; // prend en compte la config mono ligne
		else
			nbTotal_ConfigML = 1;

		// Optimisation :
		// On teste d'abord les 2 positions idéales à droite ou à gauche du site
		double xCentreMax = x + rayonSite_reel_m + distBordSiteMax_reel_m + largeurAnno_1L_reel_m/2;
		double xCentreIdeal = x + rayonSite_reel_m + distBordSiteIdeale_reel_m + largeurAnno_1L_reel_m/2;
		float geneX = (float) ((xCentreIdeal-x)/(xCentreMax-x)*0.5 + 0.5);
		float geneY = 0.5f; // milieu verticalement
		float geneML = -1; // 1 ligne
		anno_bestPlacement.setPosition(geneX, geneY, geneML);
		double noteDroite = anno_bestPlacement.computeNote_indepAutreAnno(tempData);
		if (noteDroite<0.01) {
			note_bestPlacement = noteDroite;
			geneX_bestPlacement = geneX;
			geneY_bestPlacement = geneY;
			geneML_bestPlacement = geneML;
			//System.out.println("HIITTTTTTTT");
		} else {
		// puis on regarde la position ideale à gauche (= symétrique de ce qui précède)
			geneX = 1 - geneX;
			anno_bestPlacement.setPosition(geneX, geneY, geneML);
			double noteGauche = anno_bestPlacement.computeNote_indepAutreAnno(tempData);
			if (noteGauche<0.01) {
				note_bestPlacement = noteGauche;
				geneX_bestPlacement = geneX;
				geneY_bestPlacement = geneY;
				geneML_bestPlacement = geneML;
				//System.out.println("HIITTTTTTTT");
			} else {
				// Si ces 2 positions ne conviennent pas, on teste toutes les positions possibles
				//  soit un nombre de ANNO_PREPLACEMENTANNO_NBX x ANNO_PREPLACEMENTANNO_NBY combinaisons
				for (int i=0 ; i<nbTestPlacemet_X ;i++) {
					for (int j=0 ; j<nbTestPlacemet_Y ; j++) {

						for (int k=0 ; k<nbTotal_ConfigML ; k++) {

							geneX = (float)i / nbTestPlacemet_X;
							geneY = (float)j / nbTestPlacemet_Y;
							geneML = (float)k / nbTotal_ConfigML + 0.01f;

							anno_bestPlacement.setPosition(geneX, geneY, geneML);
							double note = anno_bestPlacement.computeNote_indepAutreAnno(tempData);

							if (note<note_bestPlacement) {
								note_bestPlacement = note;
								geneX_bestPlacement = geneX;
								geneY_bestPlacement = geneY;
								geneML_bestPlacement = geneML;
							}
						}
					}
				}
			}
		}

		log += "Calcul meilleur placement independant des autres annos :\n";
		log += " geneX = " + UtilsCalcul.round(geneX_bestPlacement,2) + " , geneY = " + UtilsCalcul.round(geneY_bestPlacement,2) + "\n";
		log += " note = " + UtilsCalcul.round(note_bestPlacement, 3) + "\n";

		anno_bestPlacement.setPosition(geneX_bestPlacement, geneY_bestPlacement, geneML_bestPlacement);
	}

	public void computeAlmostBestPlacement_noInteractionAutreAnno(int nbTestPlacemet_X, int nbTestPlacemet_Y) {
		if (!hasAnno()) return;
		if (bIsPreplace()) return;

		bHasFoundPlacement_withnoInteract = false;

		// On teste un grand nombre de position de l'anno pour trouver la meilleure
		// position (hors interaction avec autres anno (intersection/ambiguite)
		double[] tempData = new double[4];

		geneX_almostbestPlacement_noIntereact=0;
		geneY_almostbestPlacement_noIntereact=0;
		geneML_almostbestPlacement_noIntereact=0;
		note_almostbestPlacement_noIntereact = 999999;
		anno_almostbestPlacement_noIntereact = new Anno(this);

		// nb de config multiligne
		int nbTotal_ConfigML;
		if (bCanBeMultiline)
			nbTotal_ConfigML = vConfigCoupureLibelle.size()+1; // prend en compte la config mono ligne
		else
			nbTotal_ConfigML = 1;

		for (int i=0 ; i<nbTestPlacemet_X ;i++) {
			for (int j=0 ; j<nbTestPlacemet_Y ; j++) {

				for (int k=0 ; k<nbTotal_ConfigML ; k++) {

					float geneX = (float)i / nbTestPlacemet_X;
					float geneY = (float)j / nbTestPlacemet_Y;
					float geneML = (float)k / nbTotal_ConfigML + 0.01f;

					anno_almostbestPlacement_noIntereact.setPosition(geneX, geneY, geneML);
					if (!canThisAnnoInteractWithOtherSite(anno_almostbestPlacement_noIntereact, false)) {

						double note = anno_almostbestPlacement_noIntereact.computeNote_indepAutreAnno(tempData);
						if (note<note_almostbestPlacement_noIntereact) {

							bHasFoundPlacement_withnoInteract = true;
							note_almostbestPlacement_noIntereact = note;
							geneX_almostbestPlacement_noIntereact = geneX;
							geneY_almostbestPlacement_noIntereact = geneY;
							geneML_almostbestPlacement_noIntereact = geneML;
						}
						
					}
				}
			}
		}

		log += "Calcul meilleur placement SANS interaction avec autres annos :\n";
		if (bHasFoundPlacement_withnoInteract) {
			anno_almostbestPlacement_noIntereact.setPosition(geneX_almostbestPlacement_noIntereact, geneY_almostbestPlacement_noIntereact, geneML_almostbestPlacement_noIntereact);
			log += " geneX = " + UtilsCalcul.round(geneX_almostbestPlacement_noIntereact,2) + " , geneY = " + UtilsCalcul.round(geneY_almostbestPlacement_noIntereact,2) + "\n";
			log += " note = " + UtilsCalcul.round(note_almostbestPlacement_noIntereact, 3) + "\n";
		} else {
			anno_almostbestPlacement_noIntereact = null;
			log += " pas trouvé d'emplacement sans interaction !! \n";
		}

	}


	// renvoie true si le placement optimal est totalement independant des autres annos
	//  et que donc le site est declare comme pre-placé
	// false sinon
	public boolean check_if_BestPosition_IndependantAutreAnno() {
		if (!hasAnno()) return false;
		// test si cette position optimale peut entrainer une interaction avec les autres annos
		anno_bestPlacement.setPosition(geneX_bestPlacement, geneY_bestPlacement, geneML_bestPlacement);
		bBestPlacementHasPossibleInteraction = canThisAnnoInteractWithOtherSite(anno_bestPlacement, true);

		// la position optimale trouvé est totalement independante des autres sites
		if (!bBestPlacementHasPossibleInteraction) {
			setPrePlaceCarIndependant(TypePreplacement.BEST, anno_bestPlacement, geneX_bestPlacement, geneY_bestPlacement, geneML_bestPlacement, note_bestPlacement);
			return true;
		} else
			return false;
	}

	// renvoie true si le placement optimal est totalement independant des autres annos
	//  et que donc le site est declare comme pre-placé
	// false sinon
	public boolean check_if_BestPositionWithNoInteract_notToBad(double seuilNote) {
		if (!hasAnno()) return false;
		// rappel, si aucune position trouvée, ce qui est possible, alors la note vaut 1
		if (note_almostbestPlacement_noIntereact<seuilNote) {
			setPrePlaceCarIndependant(TypePreplacement.ALMOSTBEST, anno_almostbestPlacement_noIntereact, geneX_almostbestPlacement_noIntereact, geneY_almostbestPlacement_noIntereact, geneML_almostbestPlacement_noIntereact, note_almostbestPlacement_noIntereact);
			return true;
		} else
			return false;
	}

	private boolean canThisAnnoInteractWithOtherSite(Anno anno, boolean bLog) {
		
		// d'abord superposition eventuelles avec d'autres sites ?
		for (Site site : tab_tabSiteIntersectAnno[Site.SSRECT_FULL]) {
			if (anno.site==site) continue;
			if (UtilsGeom.areRectIntersected(anno.rect_protection_superpositionAnno, site.rectMax_reel_m)) {
				if (bLog) log += " -> possible superposition avec " + site + "\n";
				return true;
			}
		}

		// puis l'ambiguite ALAS
		
		//on ne prend pas de dist anno-Site <0 car ca trouble la creation des rectangle de recherche ...
		double distAnnoSiteCorrigee = Math.max (0, anno.distMinBordSite);

		for (Site site : tabSiteAmbiguiteMax_Anno_Site_memeCouleur) {
			if (anno.site==site) continue;
			if (UtilsGeom.getDistRectPoint(anno, site.x, site.y) - site.rayonSite_reel_m < 2 * distAnnoSiteCorrigee) {
				if (bLog) log += " -> possible ambiguite ALAS meme couleur avec " + site + "\n";
				return true;
			}
		}

		// ON NE PREND PAS EN COMPTE L'AMBIGUITE AUTRE COULEUR, CAR CRITERE PAS IMPORTANT
		// -> cela isole mieux les annos -> on trouve plus d'anno isolées
//		for (Site site : tabSiteAmbiguiteMax_Anno_Site_autreCouleur) {
//			if (anno.site==site) continue;
//			if (UtilsGeom.getDistRectPoint(anno, site.x, site.y) - site.rayonSite_reel_m < 2 * distAnnoSiteCorrigee) {
//				if (bLog) log += " -> possible ambiguite ALAS autre couleur avec " + site + "\n";
//				return true;
//			}
//		}

		// enfin l'ambiguite ALSA
		Rect_d rectAmbiguite = new Rect_d(anno.site, anno.site.rayonSite_reel_m*2);
		rectAmbiguite.expand(2 * distAnnoSiteCorrigee, 2 * distAnnoSiteCorrigee);
		for (Site site : tabSiteAmbiguiteMax_Site_Anno_memeCouleur) {
			if (anno.site==site) continue;
			if (UtilsGeom.areRectIntersected(rectAmbiguite, site.rectMax_reel_m)) {
				if (bLog) log += " -> possible ambiguite ALSA meme couleur avec " + site + "\n";
				return true;
			}
		}

		// ON NE PREND PAS EN COMPTE L'AMBIGUITE AUTRE COULEUR, CAR CRITERE PAS IMPORTANT
		// -> cela isole mieux les annos -> on trouve plus d'anno isolées
//		for (Site site : tabSiteAmbiguiteMax_Site_Anno_autreCouleur) {
//			if (anno.site==site) continue;
//			if (UtilsGeom.areRectIntersected(rectAmbiguite, site.rectMax_reel_m)) {
//				if (bLog) log += " -> possible ambiguite ALSA autre couleur avec " + site + "\n";
//				return true;
//			}
//		}


		if (bLog) log += " -> pas d'interaction possible avec autres site" + "\n";
		return false;
	}



	//**************************************************************************
	//  OPTIMISATION / PRE-CALCULS
	//**************************************************************************
	// Optimisations très importantes, du fait que les sites et les segments ne bougent pas
	//  on peut donc précalculer beaucoup de paramètres, de proximités, etc.
	// Lancé une fois que tous les objets sont créés, car on en a besoin pour précalculer
	//  des proximités, etc.v

	public void optimize_precalculProximite (GroupeSite grpSite, GroupeSegmentFixe grpSeg) {

		// En plus des tableaux, on range tout ca en plus dans des set pour avoir une liste de sites uniques
		setSiteInteraction = new HashSet<Site>();
		setSiteInteraction_woAutreCouleur = new HashSet<Site>();


		if (!hasAnno())
			return; // pas d'annotation pour ce site

		// Optimisation des segments intersectables
		optim_IntersectSegment(grpSeg);

		// Optimisation des sites dont l'anno peut intersecter l'anno ou croiser le lien_anno_site de ce site
		optim_IntersectAnno(grpSite);

		// Optimisation de l'ambiguite de lecture Anno->Site et Site->Anno
		optim_Ambiguite(grpSite);

		// Optimisation des sites superposables
		optimiseSitesSuperposables(grpSite);


	}


	private void optimiseSitesSuperposables(GroupeSite grpSite) {
		ArrayList<Site> vSiteSuperposables = new ArrayList<Site>();
		Rect_d rMax_demiRayonSite = rectMax_reel_m.getCopy();
		rMax_demiRayonSite.expand (grpSite.getLargestRayonSite(), grpSite.getLargestRayonSite());

		int nbSite = grpSite.vSite.size();
		for (int i=0 ; i<nbSite ; i++) {
			Site s = grpSite.vSite.get(i);
			// !! on ne fait pas : if (s==this) continue
			// car en effet this doit faire partie des sites superposables à l'anno de this !!
			if (rMax_demiRayonSite.contientPt_larg(s))
				vSiteSuperposables.add (s);
		}
		tabSiteSuperposables = new Site[vSiteSuperposables.size()];
		vSiteSuperposables.toArray(tabSiteSuperposables);

		setSiteInteraction.addAll(vSiteSuperposables);
		setSiteInteraction_woAutreCouleur.addAll(vSiteSuperposables);
	}


	// Pre-calcul les 2 sortes d'ambiguite (Anno->Site et Site->Anno)
	//  et pour les 2 possibilités : meme couleur, et quelle que soit la couleur
	private void optim_Ambiguite(GroupeSite grpSite) {
		
		int nbSite = grpSite.vSite.size();

		//***********************************************************************
		// Optimisation des sites avec anno potentiellement ambigues type Anno->Site
		ArrayList<Site> vSiteAmbiguiteMax_Anno_Site_memeCouleur = new ArrayList<Site>();
		ArrayList<Site> vSiteAmbiguiteMax_Anno_Site_autreCouleur = new ArrayList<Site>();
		ArrayList<Site> vSiteAmbiguiteMedium_Anno_Site_memeCouleur = new ArrayList<Site>();
		ArrayList<Site> vSiteAmbiguiteMedium_Anno_Site_autreCouleur = new ArrayList<Site>();
		ArrayList<Site> vSiteAmbiguiteSmall_Anno_Site_memeCouleur = new ArrayList<Site>();
		ArrayList<Site> vSiteAmbiguiteSmall_Anno_Site_autreCouleur = new ArrayList<Site>();

		double hauteur1Anno;
		if (params.bUseMultiligne)
			hauteur1Anno = (2+params.facteurEspaceInterligne)*hauteurAnno_1L_reel_m;
		else
			hauteur1Anno = hauteurAnno_1L_reel_m;

		// Rect max (cas le pire si l'anno est a distMax)
		double largMax = rayonSite_reel_m + distBordSiteMax_reel_m*1 + largeurAnno_1L_reel_m + params.facteurLimiteAmbiguite*distBordSiteMax_reel_m*1 + grpSite.getLargestRayonSite(zones);
		double hautMax = rayonSite_reel_m + distBordSiteMax_reel_m*1 + hauteur1Anno       + params.facteurLimiteAmbiguite*distBordSiteMax_reel_m*1 + grpSite.getLargestRayonSite(zones);
		Rect_d rMax_ambiguity = new Rect_d(this, 2*largMax, 2*hautMax);

		// Rect medium : cas ou l'anno est a une distance <= distMax/2
		double largMedium = rayonSite_reel_m + distBordSiteMax_reel_m*factorMedium + largeurAnno_1L_reel_m + params.facteurLimiteAmbiguite*distBordSiteMax_reel_m*factorMedium + grpSite.getLargestRayonSite(zones);
		double hautMedium = rayonSite_reel_m + distBordSiteMax_reel_m*factorMedium + hauteur1Anno       + params.facteurLimiteAmbiguite*distBordSiteMax_reel_m*factorMedium + grpSite.getLargestRayonSite(zones);
		Rect_d rMedium_ambiguity = new Rect_d(this, 2*largMedium, 2*hautMedium);

		// Rect medium : cas ou l'anno est a une distance <= distMax/2
		double largSmall = rayonSite_reel_m + distBordSiteMax_reel_m*factorSmall + largeurAnno_1L_reel_m + params.facteurLimiteAmbiguite*distBordSiteMax_reel_m*factorSmall + grpSite.getLargestRayonSite(zones);
		double hautSmall = rayonSite_reel_m + distBordSiteMax_reel_m*factorSmall + hauteur1Anno       + params.facteurLimiteAmbiguite*distBordSiteMax_reel_m*factorSmall + grpSite.getLargestRayonSite(zones);
		Rect_d rSmall_ambiguity = new Rect_d(this, 2*largSmall, 2*hautSmall);


		// pre-recherche des sites potentiels grace au SPI
		Rect_d rectRecherche = rMax_ambiguity;
		ArrayList<Site> vSiteProche = grpSite.getSitesThaMayIntersectWith(rectRecherche);

		nbSite = vSiteProche.size();
		for (int i=0 ; i<nbSite ; i++) {
			Site s = vSiteProche.get(i);
			if (this == s) continue;
			if (rMax_ambiguity.contientPt_larg(s)) {
				if (this.getColor().equals(s.getColor()))
					vSiteAmbiguiteMax_Anno_Site_memeCouleur.add (s);
				else
					vSiteAmbiguiteMax_Anno_Site_autreCouleur.add (s);
			}
			if (rMedium_ambiguity.contientPt_larg(s)) {
				if (this.getColor().equals(s.getColor()))
					vSiteAmbiguiteMedium_Anno_Site_memeCouleur.add (s);
				else
					vSiteAmbiguiteMedium_Anno_Site_autreCouleur.add (s);
			}
			if (rSmall_ambiguity.contientPt_larg(s)) {
				if (this.getColor().equals(s.getColor()))
					vSiteAmbiguiteSmall_Anno_Site_memeCouleur.add (s);
				else
					vSiteAmbiguiteSmall_Anno_Site_autreCouleur.add (s);
			}

		}
		tabSiteAmbiguiteMax_Anno_Site_memeCouleur = new Site[vSiteAmbiguiteMax_Anno_Site_memeCouleur.size()];
		tabSiteAmbiguiteMax_Anno_Site_autreCouleur = new Site[vSiteAmbiguiteMax_Anno_Site_autreCouleur.size()];
		vSiteAmbiguiteMax_Anno_Site_memeCouleur.toArray(tabSiteAmbiguiteMax_Anno_Site_memeCouleur);
		vSiteAmbiguiteMax_Anno_Site_autreCouleur.toArray(tabSiteAmbiguiteMax_Anno_Site_autreCouleur);

		tabSiteAmbiguiteMedium_Anno_Site_memeCouleur = new Site[vSiteAmbiguiteMedium_Anno_Site_memeCouleur.size()];
		tabSiteAmbiguiteMedium_Anno_Site_autreCouleur = new Site[vSiteAmbiguiteMedium_Anno_Site_autreCouleur.size()];
		vSiteAmbiguiteMedium_Anno_Site_memeCouleur.toArray(tabSiteAmbiguiteMedium_Anno_Site_memeCouleur);
		vSiteAmbiguiteMedium_Anno_Site_autreCouleur.toArray(tabSiteAmbiguiteMedium_Anno_Site_autreCouleur);

		tabSiteAmbiguiteSmall_Anno_Site_memeCouleur = new Site[vSiteAmbiguiteSmall_Anno_Site_memeCouleur.size()];
		tabSiteAmbiguiteSmall_Anno_Site_autreCouleur = new Site[vSiteAmbiguiteSmall_Anno_Site_autreCouleur.size()];
		vSiteAmbiguiteSmall_Anno_Site_memeCouleur.toArray(tabSiteAmbiguiteSmall_Anno_Site_memeCouleur);
		vSiteAmbiguiteSmall_Anno_Site_autreCouleur.toArray(tabSiteAmbiguiteSmall_Anno_Site_autreCouleur);


		//***********************************************************************
		// Optimisation des sites avec anno potentiellement ambigues type Site->Anno
		ArrayList<Site> vSiteAmbiguiteMax_Site_Anno_memeCouleur = new ArrayList<Site>();
		ArrayList<Site> vSiteAmbiguiteMax_Site_Anno_autreCouleur = new ArrayList<Site>();

		ArrayList<Site> vSiteAmbiguiteMedium_Site_Anno_memeCouleur = new ArrayList<Site>();
		ArrayList<Site> vSiteAmbiguiteMedium_Site_Anno_autreCouleur = new ArrayList<Site>();

		ArrayList<Site> vSiteAmbiguiteSmall_Site_Anno_memeCouleur = new ArrayList<Site>();
		ArrayList<Site> vSiteAmbiguiteSmall_Site_Anno_autreCouleur = new ArrayList<Site>();

		double distAmbiguiteMax = params.facteurLimiteAmbiguite*distBordSiteMax_reel_m;
		double distAmbiguiteMedium = params.facteurLimiteAmbiguite*distBordSiteMax_reel_m*factorMedium;
		double distAmbiguiteSmall = params.facteurLimiteAmbiguite*distBordSiteMax_reel_m*factorSmall;

		rMax_ambiguity = new Rect_d(this, 2*distAmbiguiteMax, 2*distAmbiguiteMax);
		rMedium_ambiguity = new Rect_d(this, 2*distAmbiguiteMedium, 2*distAmbiguiteMedium);
		rSmall_ambiguity = new Rect_d(this, 2*distAmbiguiteSmall, 2*distAmbiguiteSmall);

		rectRecherche = rMax_ambiguity.getCopy();
		rectRecherche.expand(grpSite.getLargestDimensionRectMax(zones)/2, grpSite.getLargestDimensionRectMax(zones)/2);
		vSiteProche = grpSite.getSitesThaMayIntersectWith(rectRecherche);

		nbSite = vSiteProche.size();
		for (int i=0 ; i<nbSite ; i++) {
			Site s = vSiteProche.get(i);
			if (this == s) continue;
			if (UtilsGeom.areRectIntersected(rMax_ambiguity, s.rectMax_reel_m)) {
				if (this.getColor().equals(s.getColor()))
					vSiteAmbiguiteMax_Site_Anno_memeCouleur.add (s);
				else
					vSiteAmbiguiteMax_Site_Anno_autreCouleur.add (s);
			}
			if (UtilsGeom.areRectIntersected(rMedium_ambiguity, s.rectMax_reel_m)) {
				if (this.getColor().equals(s.getColor()))
					vSiteAmbiguiteMedium_Site_Anno_memeCouleur.add (s);
				else
					vSiteAmbiguiteMedium_Site_Anno_autreCouleur.add (s);
			}
			if (UtilsGeom.areRectIntersected(rSmall_ambiguity, s.rectMax_reel_m)) {
				if (this.getColor().equals(s.getColor()))
					vSiteAmbiguiteSmall_Site_Anno_memeCouleur.add (s);
				else
					vSiteAmbiguiteSmall_Site_Anno_autreCouleur.add (s);
			}
		}
		tabSiteAmbiguiteMax_Site_Anno_memeCouleur = new Site[vSiteAmbiguiteMax_Site_Anno_memeCouleur.size()];
		tabSiteAmbiguiteMax_Site_Anno_autreCouleur = new Site[vSiteAmbiguiteMax_Site_Anno_autreCouleur.size()];
		vSiteAmbiguiteMax_Site_Anno_memeCouleur.toArray(tabSiteAmbiguiteMax_Site_Anno_memeCouleur);
		vSiteAmbiguiteMax_Site_Anno_autreCouleur.toArray(tabSiteAmbiguiteMax_Site_Anno_autreCouleur);

		tabSiteAmbiguiteMedium_Site_Anno_memeCouleur = new Site[vSiteAmbiguiteMedium_Site_Anno_memeCouleur.size()];
		tabSiteAmbiguiteMedium_Site_Anno_autreCouleur = new Site[vSiteAmbiguiteMedium_Site_Anno_autreCouleur.size()];
		vSiteAmbiguiteMedium_Site_Anno_memeCouleur.toArray(tabSiteAmbiguiteMedium_Site_Anno_memeCouleur);
		vSiteAmbiguiteMedium_Site_Anno_autreCouleur.toArray(tabSiteAmbiguiteMedium_Site_Anno_autreCouleur);

		tabSiteAmbiguiteSmall_Site_Anno_memeCouleur = new Site[vSiteAmbiguiteSmall_Site_Anno_memeCouleur.size()];
		tabSiteAmbiguiteSmall_Site_Anno_autreCouleur = new Site[vSiteAmbiguiteSmall_Site_Anno_autreCouleur.size()];
		vSiteAmbiguiteSmall_Site_Anno_memeCouleur.toArray(tabSiteAmbiguiteSmall_Site_Anno_memeCouleur);
		vSiteAmbiguiteSmall_Site_Anno_autreCouleur.toArray(tabSiteAmbiguiteSmall_Site_Anno_autreCouleur);

		setSiteInteraction.addAll(vSiteAmbiguiteMax_Site_Anno_memeCouleur);
		setSiteInteraction.addAll(vSiteAmbiguiteMax_Anno_Site_memeCouleur);
		setSiteInteraction.addAll(vSiteAmbiguiteMax_Site_Anno_autreCouleur);
		setSiteInteraction.addAll(vSiteAmbiguiteMax_Anno_Site_autreCouleur);
		setSiteInteraction_woAutreCouleur.addAll(vSiteAmbiguiteMax_Site_Anno_memeCouleur);
		setSiteInteraction_woAutreCouleur.addAll(vSiteAmbiguiteMax_Anno_Site_memeCouleur);

	}

	// Optimisation des segments intersectables
	private void optim_IntersectSegment(GroupeSegmentFixe grpSegment) {
		ArrayList<SegmentFixe>[] tab_vSegmentIntersectable = new ArrayList[NB_SSRECT];
		for (int i=0; i<NB_SSRECT; i++)
			tab_vSegmentIntersectable[i] = new ArrayList<SegmentFixe>();

		// rectangle full et sous-rectangle de recherche de segments

		Rect_d rect_Larg; // rect_max elargi de la plus grosse largeur de segment
		rect_Larg = new Rect_d(rectMax_reel_m);
		rect_Larg.expand(grpSegment.getLargestSegment_reel_mm(), grpSegment.getLargestSegment_reel_mm());

		Rect_d[] tab_RectLarg = new Rect_d[NB_SSRECT];
		for (int i=0; i<NB_SSRECT; i++)
			tab_RectLarg[i] = new Rect_d(rect_Larg);

		double largestSegment = grpSegment.getLargestSegment_reel_mm();
		tab_RectLarg[SSRECT_RIGHT].xMin = this.x - largestSegment;
		tab_RectLarg[SSRECT_LEFT].xMax = this.x + largestSegment;
		tab_RectLarg[SSRECT_UP].yMin = this.y - largestSegment;
		tab_RectLarg[SSRECT_DOWN].yMax = this.y + largestSegment;

		tab_RectLarg[SSRECT_UPRIGHT].yMin = this.y - largestSegment;
		tab_RectLarg[SSRECT_UPRIGHT].xMin = this.x - largestSegment;

		tab_RectLarg[SSRECT_UPLEFT].yMin = this.y - largestSegment;
		tab_RectLarg[SSRECT_UPLEFT].xMax = this.x + largestSegment;

		tab_RectLarg[SSRECT_DOWNRIGHT].yMax = this.y + largestSegment;
		tab_RectLarg[SSRECT_DOWNRIGHT].xMin = this.x - largestSegment;

		tab_RectLarg[SSRECT_DOWNLEFT].yMax = this.y + largestSegment;
		tab_RectLarg[SSRECT_DOWNLEFT].xMax = this.x + largestSegment;

		// recherche des segments :
		// 1er sous-ensemble de seg possible grace au SPI (accelere l'optimisation)
		ArrayList<SegmentFixe> vSegmentPotentielForThisSite = grpSegment.getSegOptimThaMayIntersectWith(rect_Larg);

		// puis affinage pour les seg qui intersectent reellement rectMax
		int nbSeg = vSegmentPotentielForThisSite.size();
		double[] tempData = new double[4]; // pour optim vitesse fonction de UtilsGeom
		for (int i=0 ; i<nbSeg ; i++) {
			SegmentFixe seg = vSegmentPotentielForThisSite.get(i);
			for (int j=0; j<NB_SSRECT; j++)
				if (UtilsGeom.areRectSegIntersected__fast(tab_RectLarg[j], seg, tempData))
					tab_vSegmentIntersectable[j].add(seg);
		}

		// création des tableaux finals à partir des vecteurs
		tab_tabSegmentIntersectable = new SegmentFixe[NB_SSRECT][];
		for (int i=0; i<NB_SSRECT; i++) {
			tab_tabSegmentIntersectable[i] = new SegmentFixe[tab_vSegmentIntersectable[i].size()];
			tab_vSegmentIntersectable[i].toArray(tab_tabSegmentIntersectable[i]);
		}
	}


	// Optimisation des sites dont l'anno peut intersecter l'anno ou croiser le lien_anno_site de ce site
	private void optim_IntersectAnno(GroupeSite grpSite) {
		
		ArrayList<Site>[] tab_vSiteIntersectAnno = new ArrayList[NB_SSRECT];
		for (int i=0; i<NB_SSRECT; i++)
			tab_vSiteIntersectAnno[i] = new ArrayList<Site>();


		Rect_d[] tab_Rect = new Rect_d[NB_SSRECT];
		for (int i=0; i<NB_SSRECT; i++)
			tab_Rect[i] = new Rect_d(rectMax_reel_m);

		tab_Rect[SSRECT_RIGHT].xMin = this.x;
		tab_Rect[SSRECT_LEFT].xMax = this.x;
		tab_Rect[SSRECT_UP].yMin = this.y;
		tab_Rect[SSRECT_DOWN].yMax = this.y;

		tab_Rect[SSRECT_UPRIGHT].yMin = this.y;
		tab_Rect[SSRECT_UPRIGHT].xMin = this.x;

		tab_Rect[SSRECT_UPLEFT].yMin = this.y;
		tab_Rect[SSRECT_UPLEFT].xMax = this.x;

		tab_Rect[SSRECT_DOWNRIGHT].yMax = this.y;
		tab_Rect[SSRECT_DOWNRIGHT].xMin = this.x;

		tab_Rect[SSRECT_DOWNLEFT].yMax = this.y;
		tab_Rect[SSRECT_DOWNLEFT].xMax = this.x;

		// recherche des sites :
		Rect_d rectRecherche = rectMax_reel_m.getCopy();
		rectRecherche.expand(grpSite.getLargestDimensionRectMax(zones), grpSite.getLargestDimensionRectMax(zones));
		ArrayList<Site> vSiteProche = grpSite.getSitesThaMayIntersectWith(rectRecherche);

		int nbSite = vSiteProche.size();
		for (int i=0 ; i<nbSite ; i++) {
			Site s = vSiteProche.get(i);
			if (s == this) continue;
			if (!s.hasAnno()) continue; // c'est un site sans annotation
			
			for (int j=0; j<NB_SSRECT; j++)
				if (UtilsGeom.areRectIntersected(tab_Rect[j], s.rectMax_reel_m))
					tab_vSiteIntersectAnno[j].add(s);
		}

		// création des tableaux finals à partir des vecteurs
		tab_tabSiteIntersectAnno = new Site[NB_SSRECT][];
		for (int i=0; i<NB_SSRECT; i++) {
			tab_tabSiteIntersectAnno[i] = new Site[tab_vSiteIntersectAnno[i].size()];
			tab_vSiteIntersectAnno[i].toArray(tab_tabSiteIntersectAnno[i]);
		}

		setSiteInteraction.addAll(tab_vSiteIntersectAnno[SSRECT_FULL]);
		setSiteInteraction_woAutreCouleur.addAll(tab_vSiteIntersectAnno[SSRECT_FULL]);
	}



	//**************************************************************************
	// INTERFACE IDrawable
	//**************************************************************************

	public void draw(Graphics2D g, ParametresAffichageCarto pac) {
		Ellipse2D.Float  shape = (Ellipse2D.Float) getShape(pac);

		// Intérieur du rond
		if (pac.modeAffichage == ModeAffichage.SELECTION || pac.modeAffichage == ModeAffichage.HIGHLIGHT)
			g.setColor(pac.colorSelection);
		else
			g.setColor(color);
		g.fill(shape);

		// Bordure du rond (blanc, epaisseur = 1px
		g.setColor(Color.WHITE);
		g.setStroke(pac.getBasicStroke(1f));
		g.draw(shape);

		// Si mode highlight, on dessine un rond fushia autour du site
		if (pac.modeAffichage == ModeAffichage.HIGHLIGHT) {
			shape.x -= 4;
			shape.y -= 4;
			shape.width += 8;
			shape.height += 8;

			g.setColor(pac.colorHighlight);
			g.setStroke(pac.getBasicStroke(1.5f));
			g.draw(shape);
		}
	}

	private Shape getShape(ParametresAffichageCarto pac) {

		Ellipse2D.Float shEllipse = new Ellipse2D.Float();
		float rayon_px = pac.lg2LG(rayonSite_reel_m);
		shEllipse.x = pac.x2X(x) - rayon_px;
		shEllipse.y = pac.y2Y(y) - rayon_px;
		shEllipse.width  = rayon_px*2;
		shEllipse.height = rayon_px*2;

		return shEllipse;
	}

	@Override
	public String toString() {
		return getInfoBulle();
	}

	public String getInfoBulle() {
		if (!hasAnno())
			return "<Site sans annotation>";
		else
			return libelle;
	}

	public String getInfoDetaillee() {
		if (!hasAnno())
			return "<Site sans annotation>";
		
		String res = attributes.toString() + "\n";

		res += "\n\nParamètres géométriques de l'annotation :\n";
		res += " " + contrainteGeom;

		res += "\n\n" + log; // diverses info accumulées pendant les pré-traitements

		if (Logger.isDebugLevel()) {
			res += "\nNb site en interaction total : " + setSiteInteraction.size();
			res += "\nNb site en interaction (sans ambig autre coul) : " + setSiteInteraction_woAutreCouleur.size();
			res += "\n";

			if (params.bUseMultiligne) {
				res += "\nCoupures possibles du Libelle : " + libelle + "\n";
				res += printAllConfigCoupureLibelle();
				res += "\n";
			}

			res +=  "\n\nNb seg FULL  : " + tab_tabSegmentIntersectable[SSRECT_FULL].length + "\n" +
					"Nb seg RIGHT : " + tab_tabSegmentIntersectable[SSRECT_RIGHT].length + "\n" +
					"Nb seg LEFT  : " + tab_tabSegmentIntersectable[SSRECT_LEFT].length + "\n" +
					"Nb seg UP    : " + tab_tabSegmentIntersectable[SSRECT_UP].length + "\n" +
					"Nb seg DOWN  : " + tab_tabSegmentIntersectable[SSRECT_DOWN].length + "\n" +
					"Nb seg UP-RIGHT   : " + tab_tabSegmentIntersectable[SSRECT_UPRIGHT].length + "\n" +
					"Nb seg UP-LEFT    : " + tab_tabSegmentIntersectable[SSRECT_UPLEFT].length + "\n" +
					"Nb seg DOWN-RIGHT : " + tab_tabSegmentIntersectable[SSRECT_DOWNRIGHT].length + "\n" +
					"Nb seg DOWN-LEFT  : " + tab_tabSegmentIntersectable[SSRECT_DOWNLEFT].length + "\n";
			res += "\n";

		}
		return res;
	}

	@Override
	public boolean matchSearch(Pattern pattern) {
		if (attributes==null) return false;
		int nbAttrib = attributes.getModel().getNumberOfField();
		for (int i=0; i<nbAttrib ; i++) {
			if (pattern.matcher(attributes.getValue(i).toUpperCase()).matches())
				 return true;
		}
		return false;
	}


}
